﻿#region Disclaimer/Info

/////////////////////////////////////////////////////////////////////////////////////////////////
//
//   File:		PostService.cs
//   Website:		http://dexterblogengine.com/
//   Authors:		http://dexterblogengine.com/About.ashx
//   Rev:		1
//   Created:		19/01/2011
//   Last edit:		19/01/2011
//   License:		GNU Library General Public License (LGPL)
// 
//   For updated news and information please visit http://dexterblogengine.com/
//   Dexter is hosted to Codeplex at http://dexterblogengine.codeplex.com
//   For any question contact info@dexterblogengine.com
//
///////////////////////////////////////////////////////////////////////////////////////////////////

#endregion

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using Dexter.Caching;
using Dexter.Core.EventArgs;
using Dexter.DataTransferObject;
using Dexter.Domain.Exceptions;
using Dexter.Domain.Model;
using Dexter.Domain.Results;
using Dexter.Logger.Contracts;
using Dexter.Repository;
using Topics.Radical.Validation;

namespace Dexter.Services.Implementation {
	/// <summary>
	/// 	The implementatio of <see cref = "IPostService" />.
	/// </summary>
	public class PostService : IPostService {
		/// <summary>
		/// 	An instance of a concrete implementation of <see cref = "ICategoryRepository" />
		/// </summary>
		protected ICategoryRepository CategoryRepository;

		/// <summary>
		/// 	An instance of a concrete implementation of <see cref = "ICacheProvider" />
		/// </summary>
		protected ICacheProvider CacheProvider;

		/// <summary>
		/// 	The logger.
		/// </summary>
		protected ILogger Logger;

		/// <summary>
		/// 	An instance of a concrete implementation of <see cref = "IPostRepository" />
		/// </summary>
		protected IPostRepository PostRepository;

		/// <summary>
		/// Initializes a new instance of the <see cref="PostService"/> class.
		/// </summary>
		/// <param name="logger">The logger.</param>
		/// <param name="postRepository">The post repository.</param>
		/// <param name="categoryRepository">The category repository.</param>
		/// <param name="cacheProvider">The cache provider.</param>
		public PostService ( ILogger logger , IPostRepository postRepository , ICategoryRepository categoryRepository, ICacheProvider cacheProvider ) {
			Logger = logger;
			PostRepository = postRepository;
			CategoryRepository = categoryRepository;
			CacheProvider = cacheProvider;
		}

		#region IPostService Members

		///<summary>
		///	Retrieve the list of months for all published posts.
		///</summary>
		///<returns>
		///	Return a list of <c>MonthDTO</c> based on <c>PublishDate</c> property of <c>Item</c>.
		///	It could be null if there aren't items into the repository.
		///</returns>
		public virtual IList<Month> GetMonths()
		{
			var e = new CancelEventArgs ( );

			MonthRetrieving.Raise ( this , e );

			if ( e.Cancel ) {
				return null;
			}

			var value = CacheProvider.Get<IList<Month>>("GetMonths", CacheRegions.Posts)
				.Return ( o => o,
						  ( ) =>
						  {
							  var c = PostRepository.GetMonths ( false );

							  var data = PostRepository.GetMonths ( );

							  MonthRetrieved.Raise(this, new GenericEventArgs<IList<Month>>(data));

							  CacheProvider.Put ( "GetMonths", c, TimeSpan.FromHours ( 5 ), CacheRegions.Posts, null );

							  return c;
						  } );

			return value;
		}

		///<summary>
		///	Retrieve a post for a specified key.
		///</summary>
		///<param name = "key">The key of the post.</param>
		///<returns>
		///	An instance of <see cref = "Post" />.
		///	It could be null if there isn't an item with the specified key into the repository.
		///</returns>
		public virtual Post GetByKey ( int key ) {
			return GetByKey ( key , true );
		}

		///<summary>
		///	Retrieve a post for a specified key.
		///</summary>
		///<param name = "key">The key of the post.</param>
		///<returns>
		///	An instance of <see cref="PostDto"/>.
		///	It could be null if there isn't an item with the specified key into the repository.
		///</returns>
		/// <remarks>
		///	The object result will be returned with tracking disabled. Pay attetion, or use the overload with enableTracking parameter.
		/// </remarks>
		public PostDto GetPostDtoByKey ( int key ) {
			var value = CacheProvider.Get <PostDto> ( key.ToString ( ) , CacheRegions.Posts )
				.Return ( o => o ,
				          ( ) => {
								var c = PostRepository.GetByKey( key, false ).ToDto ( );

				          	CacheProvider.Put ( key.ToString ( ) , c , TimeSpan.FromMinutes ( 10 ) , CacheRegions.Posts , null );

				          	return c;
				          } );

			return value;
		}

		///<summary>
		///	Retrieve a post for a specified key.
		///</summary>
		///<param name = "key">The key of the post.</param>
		///<param name = "enableTracking">if set to <c>true</c> the tracking system of the return object will be enable.</param>
		///<returns>
		///	An instance of <see cref="Post"/>.
		///	It could be null if there isn't an item with the specified key into the repository.
		///</returns>
		public Post GetByKey ( int key , bool enableTracking ) {
			var e = new CancelEventArgsWithOneParameter<int> ( key );

			PostRetrievingByKey.Raise ( this, e );

			if (e.Cancel) {
				return null;
			}

			var data = PostRepository.GetByKey ( key, enableTracking );

			PostRetrievedByKey.Raise ( this, new GenericEventArgsWithOneParameter<Post, int> ( data, key ) );

			return data;
		}

		///<summary>
		///	Retrieve a post for a specified slug.
		///</summary>
		///<param name = "slug">The slug.</param>
		///<returns>
		///	An instance of <see cref = "Post" />.
		///	It could be null if there isn't an item with the specified slug into the repository.
		///</returns>
		public virtual Post GetBySlug ( string slug ) {
			return GetBySlug ( slug , true );
		}

		///<summary>
		///	Retrieve a post for a specified slug.
		///</summary>
		///<param name = "slug">The slug.</param>
		///<returns>
		///	An instance of <see cref="PostDto"/>.
		///	It could be null if there isn't an item with the specified slug into the repository.
		///</returns>
		public PostDto GetPostDtoBySlug ( string slug ) {
			var value = CacheProvider.Get<PostDto> ( slug, CacheRegions.Posts )
				.Return ( o => o,
						  ( ) =>
						  {
							  var c = PostRepository.GetBySlug ( slug, false ).ToDto ( );

							  CacheProvider.Put ( slug, c, TimeSpan.FromMinutes ( 10 ), CacheRegions.Posts, null );

							  return c;
						  } );

			return value;
		}

		///<summary>
		///	Retrieve a post for a specified slug.
		///</summary>
		///<param name = "slug">The slug.</param>
		///<param name = "enableTracking">if set to <c>true</c> the tracking system of the return object will be enable.</param>
		///<returns>
		///	An instance of <see cref="Post"/>.
		///	It could be null if there isn't an item with the specified slug into the repository.
		///</returns>
		/// <remarks>
		///	The object result will be returned with tracking disabled. Pay attetion, or use the overload with enableTracking parameter.
		/// </remarks>
		public Post GetBySlug ( string slug , bool enableTracking ) {
			var e = new CancelEventArgsWithOneParameter<string> ( slug );

			PostRetrievingBySlug.Raise ( this, e );

			if (e.Cancel) {
				return null;
			}

			var data = PostRepository.GetBySlug ( slug, enableTracking );

			PostRetrievedBySlug.Raise ( this, new GenericEventArgsWithOneParameter<Post, string> ( data, slug ) );

			return data;
		}

		///<summary>
		///	Deletes the post with the specified key, from the repository.
		///</summary>
		///<param name = "key">The key.</param>
		///<exception cref = "ArgumentOutOfRangeException"> If <paramref name = "key" /> is lesser than 1.</exception>
		///<exception cref = "PostNotFoundException"> If the <see cref = "Post" /> will not found into the repository.</exception>
		public virtual void Delete ( int key ) {
			Ensure.That ( key )
				.Named ( "key" )
				.WithPreview ( ( v , e ) => Logger.Warn ( v.GetFullErrorMessage ( ) , e ) )
				.IsGreaterThen ( 0 )
				.WithMessage ( "Invalid post key." );

			var ea = new CancelEventArgsWithOneParameter <int> ( key );

			PostDeleting.Raise ( this , ea );

			if ( ea.Cancel ) {
				return;
			}

			var post = GetByKey ( key );

			PostRepository.Delete ( post );

			CacheProvider.ClearRegion ( CacheRegions.Posts );

			PostDeleted.Raise ( this , new GenericEventArgs <Post> ( post ) );
		}

		/// <summary>
		/// 	Saves the specified post into the repository.
		/// </summary>
		/// <param name = "item">The item.</param>
		public virtual void Save ( Post item ) {
			var ea = new CancelEventArgsWithOneParameter <Post> ( item );

			PostSaving.Raise ( this , ea );

			if ( ea.Cancel ) {
				return;
			}

			PrepareItemToSave ( item );

			PostRepository.Save ( item );

			CacheProvider.ClearRegion ( CacheRegions.Posts );

			PostSaved.Raise ( this , new GenericEventArgs <Post> ( item ) );
		}

		/// <summary>
		/// 	Updates the specified post into the repository.
		/// </summary>
		/// <param name = "item">The item.</param>
		public virtual void Update ( Post item ) {
			var ea = new CancelEventArgsWithOneParameter <Post> ( item );

			PostUpdating.Raise ( this , ea );

			if ( ea.Cancel ) {
				return;
			}

			PrepareItemToSave ( item );

			PostRepository.Update ( item );

			CacheProvider.ClearRegion ( CacheRegions.Posts );

			PostUpdated.Raise ( this , new GenericEventArgs <Post> ( item ) );
		}

		///<summary>
		///	Retireve a paged list of all posts with the property <see cref = "Item.CommentEnabled" /> set to <c>false</c>.
		///</summary>
		///<remarks>
		///	The result will include only published past posts.
		///	Moreover the result hasn't the tracking system enabled
		///</remarks>
		///<param name = "pageIndex">Index of the page (0 based).</param>
		///<param name = "pageSize">The number of items to retrieve.</param>
		///<returns>An concrete implementation of <see cref = "IPagedResult{T}" />. The result will never be null.</returns>
		public virtual IPagedResult <Post> GetPublishedPostWithCommentsDisabled ( int pageIndex , int pageSize ) {
			Tuple <int , int> t = new Tuple <int , int> ( pageIndex , pageSize );

			var e = new CancelEventArgsWithOneParameter <Tuple <int , int>> ( t );

			PostsWithCommentsEnabledRetrieving.Raise ( this , e );

			if ( e.Cancel ) {
				return new EmptyPagedResult <Post> ( pageIndex , pageSize );
			}

			var data = PostRepository.GetPublishedPostWithCommentsDisabled ( pageIndex , pageSize , true );

			PostsWithCommentsEnabledRetrieved.Raise ( this , new GenericEventArgs <IPagedResult <Post>> ( data ) );

			return data;
		}

		///<summary>
		///	Retireve a paged list of all posts with the property <see cref="Item.CommentEnabled"/> set to <c>false</c>.
		///</summary>
		/// <remarks>
		///		The result will include only published past posts.
		///		Moreover the result hasn't the tracking system enabled
		/// </remarks>
		///<param name = "pageIndex">Index of the page (0 based).</param>
		///<param name = "pageSize">The number of items to retrieve.</param>
		///<returns>An concrete implementation of <see cref="IPagedResult{T}"/>. The result will never be null.</returns>
		public IPagedResult <PostDto> GetPublishedPostDtoWithCommentsDisabled ( int pageIndex , int pageSize ) {
			string key = string.Format ( "GetPublishedPostDtoWithCommentsDisabled-{0}-{1}" , pageIndex , pageSize );
			
			var value = CacheProvider.Get<IPagedResult<PostDto>> ( key, CacheRegions.Posts )
				.Return ( o => o,
						  ( ) => {
						  	var c = PostRepository.GetPublishedPostWithCommentsDisabled ( pageIndex , pageSize , false ).ToDtos ( );

							CacheProvider.Put ( key, c, TimeSpan.FromMinutes ( 5 ), CacheRegions.Posts, null );

							  return c;
						  } );

			return value;
		}

		///<summary>
		///	Retireve a paged list of all posts.
		///</summary>
		///<remarks>
		///	The result will include published, unpublished, future and past posts.
		///</remarks>
		///<param name = "pageIndex">Index of the page (0 based).</param>
		///<param name = "pageSize">The number of items to retrieve.</param>
		///<returns>An concrete implementation of <see cref = "IPagedResult{T}" />. The result will never be null.</returns>
		public virtual IPagedResult <Post> GetCompleteList ( int pageIndex , int pageSize ) {
			return GetCompleteList ( pageIndex , pageSize , true );
		}

		///<summary>
		///	Retireve a paged list of all posts.
		///</summary>
		/// <remarks>
		///		The result will include published, unpublished, future and past posts.
		///		Moreover the result hasn't the tracking system enabled
		/// </remarks>
		///<param name = "pageIndex">Index of the page (0 based).</param>
		///<param name = "pageSize">The number of items to retrieve.</param>
		///<returns>An concrete implementation of <see cref="IPagedResult{T}"/>. The result will never be null.</returns>
		public IPagedResult <PostDto> GetCompleteListOfPostDtos ( int pageIndex , int pageSize ) {
			string key = string.Format ( "GetCompleteListOfPostDtos-{0}-{1}", pageIndex, pageSize );

			var value = CacheProvider.Get<IPagedResult<PostDto>> ( key, CacheRegions.Posts )
				.Return ( o => o,
						  ( ) =>
						  {
							  var c = PostRepository.GetCompleteList ( pageIndex, pageSize, false ).ToDtos ( );

							  CacheProvider.Put ( key, c, TimeSpan.FromMinutes ( 5 ), CacheRegions.Posts, null );

							  return c;
						  } );

			return value;
		}

		///<summary>
		///	Retireve a paged list of all posts.
		///</summary>
		/// <remarks>
		///		The result will include published, unpublished, future and past posts.
		///		Moreover the result hasn't the tracking system enabled
		/// </remarks>
		///<param name = "pageIndex">Index of the page (0 based).</param>
		///<param name = "pageSize">The number of items to retrieve.</param>
		/// <param name = "enableTracking">if set to <c>true</c> the tracking system of the return object will be enable.</param>
		///<returns>An concrete implementation of <see cref="IPagedResult{T}"/>. The result will never be null.</returns>
		public IPagedResult <Post> GetCompleteList ( int pageIndex , int pageSize , bool enableTracking ) {
			var e = new CancelEventArgs ( );

			PostsRetrieving.Raise ( this, e );

			if (e.Cancel) {
				return new EmptyPagedResult<Post> ( pageIndex, pageSize );
			}

			var data = PostRepository.GetCompleteList ( pageIndex, pageSize, enableTracking );

			PostsRetrieved.Raise ( this, new GenericEventArgs<IPagedResult<Post>> ( data ) );

			return data;
		}

		///<summary>
		///	Return a list approved posts, sorted by publish date.
		///</summary>
		///<param name = "numberOfItems">The number of items.</param>
		///<returns>
		///	Return a list of <see cref = "Post" />.
		///	The return item could be null if there isn't items into the repository.
		///</returns>
		public virtual IEnumerable <Post> GetRecent ( int numberOfItems ) {
			var e = new CancelEventArgsWithOneParameter <int> ( numberOfItems );

			PostsRecentRetrieving.Raise ( this , e );

			if ( e.Cancel ) {
				return null;
			}

			var data = PostRepository.GetRecent ( numberOfItems , false , true );

			PostsRecentRetrieved.Raise ( this , new GenericEventArgsWithOneParameter <IEnumerable <Post> , int> ( data , numberOfItems ) );

			return data;
		}

		///<summary>
		///	Return a list approved posts, sorted by publish date.
		///</summary>
		///<param name = "numberOfItems">The number of items.</param>
		///<returns>
		///	Return a list of <see cref="Post"/>.
		///	The return item could be null if there isn't items into the repository.
		///</returns>
		public IList<PostDto> GetRecentPostDto(int numberOfItems)
		{
			string key = string.Format ( "GetRecentPostDto-{0}", numberOfItems );

			var value = CacheProvider.Get<IList<PostDto>>(key, CacheRegions.Tollerant)
					.Return ( o => o,
						  ( ) =>
						  {
							  var c = PostRepository.GetRecent ( numberOfItems, false ).ToDtos ( );

							  CacheProvider.Put ( key, c, TimeSpan.FromMinutes ( 10 ), CacheRegions.Tollerant, null );

							  return c;
						  });

			return value;
		}

		///<summary>
		///	Return a list of posts for a specified month.
		///</summary>
		///<param name = "month">The id of the month (1 for january, 2 for february, etc).</param>
		///<param name = "year">The year (2010, 1978, etc).</param>
		///<param name = "pageIndex">Index of the page (0 based).</param>
		///<param name = "pageSize">The number of items to retrieve.</param>
		///<returns>An concrete implementation of <see cref = "IPagedResult{T}" />. The result will never be null.</returns>
		public virtual IPagedResult <Post> GetByMonth ( int month , int year , int pageIndex , int pageSize ) {
			var parameters = new Tuple <int , int> ( month , year );
			var e = new CancelEventArgsWithOneParameter <Tuple <int , int>> ( parameters );

			PostsRetrievingByMonth.Raise ( this , e );

			if ( e.Cancel ) {
				return new EmptyPagedResult <Post> ( pageIndex , pageSize );
			}

			var data = PostRepository.GetByMonth ( month , year , pageIndex , pageSize , true ,true);

			PostsRetrievedByMonth.Raise ( this , new GenericEventArgsWithOneParameter <IPagedResult <Post> , Tuple <int , int>> ( data , parameters ) );

			return data;
		}

		///<summary>
		///	Return a list of posts for a specified month.
		///</summary>
		///<param name = "month">The id of the month (1 for january, 2 for february, etc).</param>
		///<param name = "year">The year (2010, 1978, etc).</param>
		///<param name = "pageIndex">Index of the page (0 based).</param>
		///<param name = "pageSize">The number of items to retrieve.</param>
		///<returns>An concrete implementation of <see cref="IPagedResult{T}"/>. The result will never be null.</returns>
		public IPagedResult <PostDto> GetPostDtoByMonth ( int month , int year , int pageIndex , int pageSize ) {
			string key = string.Format ( "GetPostDtoByMonth-{0}-{1}-{2}-{3}" , month , year , pageIndex , pageSize );

			var value = CacheProvider.Get<IPagedResult<PostDto>> ( key, CacheRegions.Posts )
				.Return ( o => o,
						  ( ) =>
						  {
							  var c = PostRepository.GetByMonth ( month, year, pageIndex, pageSize, false ).ToDtos ( );

							  CacheProvider.Put ( key, c, TimeSpan.FromMinutes ( 10 ), CacheRegions.Posts, null );

							  return c;
						  } );

			return value;
		}

		///<summary>
		///	Retireve a paged list of approved posts.
		///</summary>
		///<param name = "pageIndex">Index of the page (0 based).</param>
		///<param name = "pageSize">The number of items to retrieve.</param>
		///<returns>An concrete implementation of <see cref = "IPagedResult{T}" />. The result will never be null.</returns>
		public virtual IPagedResult <Post> GetPublished ( int pageIndex , int pageSize ) {
			var e = new CancelEventArgs ( );

			PostsPublishedRetrieving.Raise ( this , e );

			if ( e.Cancel ) {
				return new EmptyPagedResult <Post> ( pageIndex , pageSize );
			}

			var data = PostRepository.GetPublished ( pageIndex , pageSize , false ,true);

			PostsPublishedRetrieved.Raise ( this , new GenericEventArgs <IPagedResult <Post>> ( data ) );

			return data;
		}

		///<summary>
		///	Retrieve a paged list of approved posts.
		///</summary>
		///<param name = "pageIndex">Index of the page (0 based).</param>
		///<param name = "pageSize">The number of items to retrieve.</param>
		///<returns>An concrete implementation of <see cref="IPagedResult{T}"/>. The result will never be null.</returns>
		public IPagedResult <PostDto> GetPublishedPostDto ( int pageIndex , int pageSize ) {
			string key = string.Format ( "GetPublishedPostDto-{0}-{1}", pageIndex, pageSize );

			var value = CacheProvider.Get<IPagedResult<PostDto>> ( key, CacheRegions.Posts )
				.Return ( o => o,
						  ( ) =>
						  {
							  var c = PostRepository.GetPublished ( pageIndex, pageSize, false ).ToDtos ( );

							  //the first three pages are a different timeout
							  int minutes = pageIndex < 3 ? 120 : 20;

							  CacheProvider.Put ( key, c, TimeSpan.FromMinutes ( minutes ), CacheRegions.Posts, null );

							  return c;
						  } );

			return value;
		}

		/// <summary>
		/// 	Retireve a paged list of <see cref = "Post" /> filtered by the specified <see cref = "Category" />.
		/// </summary>
		/// <param name = "category">The filter category.</param>
		/// <param name = "pageIndex">Index of the page (0 based).</param>
		/// <param name = "pageSize">The number of items to retrieve.</param>
		/// <returns>
		/// 	An concrete implementation of <see cref = "IPagedResult{T}" />. The result will never be null.
		/// </returns>
		/// <remarks>
		/// 	The result will include only published past posts, unpublished and future will be excluded from the result.
		/// 	Moreover the result hasn't the tracking system enabled
		/// </remarks>
		/// <exception cref = "ArgumentNullException"> If <paramref name = "category" /> is null.</exception>
		public virtual IPagedResult <Post> GetPostsByCategory ( Category category , int pageIndex , int pageSize ) {
			Ensure.That ( category )
				.Named ( "category" )
				.WithPreview ( ( v , e ) => Logger.Warn ( v.GetFullErrorMessage ( ) , e ) )
				.IsNotNull ( )
				.WithMessage ( "Invalid category." );

			Tuple <Category , int , int> t = new Tuple <Category , int , int> ( category , pageIndex , pageSize );

			var ea = new CancelEventArgsWithOneParameter <Tuple <Category , int , int>> ( t );

			PostsRetrievingByCategory.Raise ( this , ea );

			if ( ea.Cancel ) {
				return new EmptyPagedResult <Post> ( pageIndex , pageSize );
			}

			var data = PostRepository.GetByCategory ( category.Id , pageIndex , pageSize,true );

			PostsRetrievedByCategory.Raise ( this , new GenericEventArgs <IPagedResult <Post>> ( data ) );

			return data;
		}

		///<summary>
		///	Retireve a paged list of <see cref="PostDto"/> filtered by the specified <see cref="CategoryDto"/>.
		///</summary>
		/// <remarks>
		///		The result will include only published past posts, unpublished and future will be excluded from the result.
		///		Moreover the result hasn't the tracking system enabled
		/// </remarks>
		///<param name="category">The filter category.</param>
		///<param name = "pageIndex">Index of the page (0 based).</param>
		///<param name = "pageSize">The number of items to retrieve.</param>
		///<returns>An concrete implementation of <see cref="IPagedResult{T}"/>. The result will never be null.</returns>
		/// <exception cref="ArgumentNullException"> If <paramref name="category"/> is null.</exception>
		public IPagedResult <PostDto> GetPostsDtoByCategory ( CategoryDto category , int pageIndex , int pageSize ) {
			if(category == null)
				throw new ArgumentNullException ( "category", "Invalid category" );

			string key = string.Format ( "GetPostsDtoByCategory-{0}-{1}-{2}-{3}",category.Id, category.Name, pageIndex, pageSize );

			var value = CacheProvider.Get<IPagedResult<PostDto>> ( key, CacheRegions.Posts )
				.Return ( o => o,
						  ( ) =>
						  {
							  var c = PostRepository.GetByCategory ( category.Id, pageIndex, pageSize, false ).ToDtos ( );

							  CacheProvider.Put ( key, c, TimeSpan.FromMinutes ( 10 ), CacheRegions.Posts, null );

							  return c;
						  } );

			return value;
		}

		///<summary>
		///	Retireve a paged list of unapproved posts.
		///</summary>
		///<param name = "pageIndex">Index of the page (0 based).</param>
		///<param name = "pageSize">The number of items to retrieve.</param>
		///<param name = "includeFuturesPosts">if set to <c>true</c> the result will include the futures posts.</param>
		///<returns>An concrete implementation of <see cref = "IPagedResult{T}" />. The result will never be null.</returns>
		public virtual IPagedResult <Post> GetUnpublished ( int pageIndex , int pageSize , bool includeFuturesPosts ) {
			return GetUnpublished ( pageIndex , pageSize , includeFuturesPosts , true );
		}

		///<summary>
		///	Retireve a paged list of unapproved posts.
		///</summary>
		///<param name = "pageIndex">Index of the page (0 based).</param>
		///<param name = "pageSize">The number of items to retrieve.</param>
		///<param name="includeFuturesPosts">if set to <c>true</c> the result will include the futures posts.</param>
		///<returns>An concrete implementation of <see cref="IPagedResult{T}"/>. The result will never be null.</returns>
		public IPagedResult <PostDto> GetUnpublishedPostDto ( int pageIndex , int pageSize , bool includeFuturesPosts ) {
			string key = string.Format ( "GetUnpublishedPostDto-{0}-{1}-{2}", pageIndex, pageSize, includeFuturesPosts );

			var value = CacheProvider.Get<IPagedResult<PostDto>> ( key, CacheRegions.Posts )
				.Return ( o => o,
						  ( ) =>
						  {
							  var c = PostRepository.GetUnpublished ( pageIndex, pageSize, includeFuturesPosts, false ).ToDtos ( );

							  CacheProvider.Put ( key, c, TimeSpan.FromMinutes ( 5 ), CacheRegions.Posts, null );

							  return c;
						  } );

			return value;
		}

		///<summary>
		///	Retireve a paged list of unapproved posts.
		///</summary>
		///<param name = "pageIndex">Index of the page (0 based).</param>
		///<param name = "pageSize">The number of items to retrieve.</param>
		///<param name = "includeFuturesPosts">if set to <c>true</c> the result will include the futures posts.</param>
		///<param name = "enableTracking">if set to <c>true</c> the tracking system of the return object will be enable.</param>
		///<returns>An concrete implementation of <see cref = "IPagedResult{T}" />. The result will never be null.</returns>
		///<exception cref = "ArgumentOutOfRangeException"> If <paramref name = "pageIndex" /> is lesser than 0.</exception>
		///<exception cref = "ArgumentOutOfRangeException"> If <paramref name = "pageSize" /> is lesser than 1.</exception>
		public virtual IPagedResult <Post> GetUnpublished ( int pageIndex , int pageSize , bool includeFuturesPosts , bool enableTracking ) {
			Tuple <int , int , bool , bool> t = new Tuple <int , int , bool , bool> ( pageIndex , pageSize , includeFuturesPosts , enableTracking );

			var e = new CancelEventArgsWithOneParameter <Tuple <int , int , bool , bool>> ( t );

			PostsUnpublishedRetrieving.Raise ( this , e );

			if ( e.Cancel ) {
				return new EmptyPagedResult <Post> ( pageIndex , pageSize );
			}

			var data = PostRepository.GetUnpublished ( pageIndex , pageSize , enableTracking,true );

			PostsUnpublishedRetrieved.Raise ( this , new GenericEventArgs <IPagedResult <Post>> ( data ) );

			return data;
		}

		/// <summary>
		/// 	Retireve a paged list of unapproved posts with <see cref = "Item.PublishDate" /> equal to <paramref name = "date" />.
		/// </summary>
		/// <param name = "pageIndex">Index of the page (0 based).</param>
		/// <param name = "pageSize">The number of items to retrieve.</param>
		/// <param name = "date">The date.</param>
		/// <returns>
		/// 	An concrete implementation of <see cref = "IPagedResult{T}" />. The result will never be null.
		/// </returns>
		/// <exception cref = "ArgumentOutOfRangeException"> If <paramref name = "pageIndex" /> is lesser than 0.</exception>
		/// <exception cref = "ArgumentOutOfRangeException"> If <paramref name = "pageSize" /> is lesser than 1.</exception>
		public virtual IPagedResult <Post> GetUnpublishedForTheSpecifiedDate ( int pageIndex , int pageSize , DateTime date ) {
			return GetUnpublishedForTheSpecifiedDate ( pageSize , pageIndex , date , true );
		}

		/// <summary>
		/// Retireve a paged list of unapproved posts with <see cref="Item.PublishDate"/> equal to <paramref name="date"/>.
		/// </summary>
		/// <param name="pageIndex">Index of the page (0 based).</param>
		/// <param name="pageSize">The number of items to retrieve.</param>
		/// <param name="date">The date.</param>
		/// <returns>
		/// An concrete implementation of <see cref="IPagedResult{T}"/>. The result will never be null.
		/// </returns>
		public IPagedResult <PostDto> GetUnpublishedPostDtoForTheSpecifiedDate ( int pageIndex , int pageSize , DateTime date ) {
			string key = string.Format ( "GetUnpublishedPostDtoForTheSpecifiedDate-{0}-{1}-{2}", pageIndex, pageSize, date.Ticks );

			var value = CacheProvider.Get<IPagedResult<PostDto>> ( key, CacheRegions.Posts )
				.Return ( o => o,
						  ( ) => {
						  	var c = PostRepository.GetUnpublishedForTheSpecifiedDate ( pageIndex , pageSize , date, false ).ToDtos ( );

							CacheProvider.Put ( key, c, TimeSpan.FromMinutes ( 5 ), CacheRegions.Posts, null );

							  return c;
						  } );

			return value;
		}

		/// <summary>
		/// 	Retireve a paged list of unapproved posts with <see cref = "Item.PublishDate" /> equal to <paramref name = "date" />.
		/// </summary>
		/// <param name = "pageIndex">Index of the page (0 based).</param>
		/// <param name = "pageSize">The number of items to retrieve.</param>
		/// <param name = "date">The date.</param>
		/// <param name = "enableTracking">if set to <c>true</c> the tracking system of the return object will be enable.</param>
		/// <returns>
		/// 	An concrete implementation of <see cref = "IPagedResult{T}" />. The result will never be null.
		/// </returns>
		/// <exception cref = "ArgumentOutOfRangeException"> If <paramref name = "pageIndex" /> is lesser than 0.</exception>
		/// <exception cref = "ArgumentOutOfRangeException"> If <paramref name = "pageSize" /> is lesser than 1.</exception>
		public virtual IPagedResult <Post> GetUnpublishedForTheSpecifiedDate ( int pageIndex , int pageSize , DateTime date , bool enableTracking ) {
			Tuple <int , int , DateTime , bool> t = new Tuple <int , int , DateTime , bool> ( pageIndex , pageSize , date , enableTracking );

			var e = new CancelEventArgsWithOneParameter <Tuple <int , int , DateTime , bool>> ( t );

			PostsUnpublishedForSpecifiedDayRetrieving.Raise ( this , e );

			if ( e.Cancel ) {
				return new EmptyPagedResult <Post> ( pageIndex , pageSize );
			}

			var data = PostRepository.GetUnpublishedForTheSpecifiedDate ( pageIndex , pageSize , date , enableTracking );

			PostsUnpublishedForSpecifiedDayRetrieved.Raise ( this , new GenericEventArgs <IPagedResult <Post>> ( data ) );

			return data;
		}

		/// <summary>
		/// 	Retireve a paged list of approved posts with <see cref = "Item.PublishDate" /> greater then <paramref name = "startDate" /> and lesser then <paramref name = "endDate" />.
		/// </summary>
		/// <param name = "pageIndex">Index of the page (0 based).</param>
		/// <param name = "pageSize">The number of items to retrieve.</param>
		/// <param name = "startDate">The start date.</param>
		/// <param name = "endDate">The end date.</param>
		/// <returns>
		/// 	An concrete implementation of <see cref = "IPagedResult{T}" />. The result will never be null.
		/// </returns>
		public virtual IPagedResult <Post> GetPublishedPostForSpecifiedPublishDateRange ( int pageIndex , int pageSize , DateTime startDate , DateTime endDate ) {
			return PostRepository.GetPublishedPostForSpecifiedPublishDateRange ( pageIndex , pageSize , startDate , endDate );
		}

		/// <summary>
		/// 	This event will raise before a list of <see cref = "Post" /> will be retrieved by the implementation of <see cref = "GetUnpublishedForTheSpecifiedDate(int,int,DateTime,bool)" />.
		/// </summary>
		public virtual event EventHandler <GenericEventArgs <IPagedResult <Post>>> PostsUnpublishedForSpecifiedDayRetrieved;

		/// <summary>
		/// 	This event will raise after a list of <see cref = "Post" /> will be retrieved by the implementation of <see cref = "GetUnpublishedForTheSpecifiedDate(int,int,DateTime,bool)" />.
		/// </summary>
		public virtual event EventHandler <CancelEventArgsWithOneParameter <Tuple <int , int , DateTime , bool>>> PostsUnpublishedForSpecifiedDayRetrieving;

		/// <summary>
		/// 	This event will raise before a list of <see cref = "Post" /> will be retrieved by the implementation of <see cref = "GetUnpublished(int,int,bool,bool)" />.
		/// </summary>
		public virtual event EventHandler <GenericEventArgs <IPagedResult <Post>>> PostsUnpublishedRetrieved;

		/// <summary>
		/// 	This event will raise after a list of <see cref = "Post" /> will be retrieved by the implementation of <see cref = "GetUnpublished(int,int,bool,bool)" />.
		/// </summary>
		public virtual event EventHandler <CancelEventArgsWithOneParameter <Tuple <int , int , bool , bool>>> PostsUnpublishedRetrieving;

		/// <summary>
		/// 	This event will raise before a list of <see cref = "Post" /> will be retrieved by the implementation of <see cref = "GetPublishedPostWithCommentsDisabled(int,int)" />.
		/// </summary>
		public virtual event EventHandler <GenericEventArgs <IPagedResult <Post>>> PostsWithCommentsEnabledRetrieved;

		/// <summary>
		/// 	This event will raise after a list of <see cref = "Post" /> will be retrieved by the implementation of <see cref = "GetPublishedPostWithCommentsDisabled(int,int)" />.
		/// </summary>
		public virtual event EventHandler <CancelEventArgsWithOneParameter <Tuple <int , int>>> PostsWithCommentsEnabledRetrieving;

		/// <summary>
		/// 	This event will raise before a list of <see cref = "Post" /> will be retrieved by the implementation of <see cref = "GetPostsByCategory(Category,int,int)" />.
		/// </summary>
		public virtual event EventHandler <GenericEventArgs <IPagedResult <Post>>> PostsRetrievedByCategory;

		/// <summary>
		/// 	This event will raise after a list of <see cref = "Post" /> will be retrieved by the implementation of <see cref = "GetPostsByCategory(Category,int,int)" />.
		/// </summary>
		public virtual event EventHandler <CancelEventArgsWithOneParameter <Tuple <Category , int , int>>> PostsRetrievingByCategory;

		/// <summary>
		/// 	This event will raise before a list of <see cref = "Post" /> will be retrieved by the implementation of <see cref = "GetRelatedContent(Post, int, int)" />.
		/// </summary>
		public virtual event EventHandler <CancelEventArgsWithOneParameter <Tuple <Post , int , int>>> PostsRetrievingByRelated;

		/// <summary>
		/// 	This event will raise when a list of <see cref = "Post" /> will be retrieved by the implementation of <see cref = "GetRelatedContent(Post, int, int)" />.
		/// </summary>
		public virtual event EventHandler <GenericEventArgs <IPagedResult <Post>>> PostsRetrievedByRelated;

		/// <summary>
		/// 	This event will raise before a list of <see cref = "Month" /> will beretrieved by the implementation of <see cref = " GetMonths()" />.
		/// </summary>
		public virtual event EventHandler <CancelEventArgs> MonthRetrieving;

		/// <summary>
		/// 	This event will raise when a list of <see cref = "Month" /> will be retrieved by the implementation of <see cref = " GetMonths()" />.
		/// </summary>
		public virtual event EventHandler<GenericEventArgs<IList<Month>>> MonthRetrieved;

		/// <summary>
		/// 	This event will raise before a list of <see cref = "Post" /> will beretrieved by the implementation of <see cref = " GetRecent(int)" />.
		/// </summary>
		public virtual event EventHandler <CancelEventArgs> PostsRecentRetrieving;

		/// <summary>
		/// 	This event will raise when a list of <see cref = "Post" /> will be retrieved by the implementation of <see cref = " GetRecent(int)" />.
		/// </summary>
		public virtual event EventHandler <GenericEventArgsWithOneParameter <IEnumerable <Post> , int>> PostsRecentRetrieved;

		/// <summary>
		/// 	This event will raise before a list of <see cref = "Post" /> will be retrieved by the implementation of <see cref = " GetBySlug(string)" />.
		/// </summary>
		public virtual event EventHandler <CancelEventArgsWithOneParameter <string>> PostRetrievingBySlug;

		/// <summary>
		/// 	This event will raise when a list of <see cref = "Post" /> will be retrieved by the implementation of <see cref = " GetBySlug(string)" />.
		/// </summary>
		public virtual event EventHandler <GenericEventArgsWithOneParameter <Post , string>> PostRetrievedBySlug;

		/// <summary>
		/// 	This event will raise before a list of <see cref = "Post" /> will be retrieved by the implementation of <see cref = "GetByKey(int)" />.
		/// </summary>
		public virtual event EventHandler <CancelEventArgsWithOneParameter <int>> PostRetrievingByKey;

		/// <summary>
		/// 	This event will raise when a list of <see cref = "Post" /> will be retrieved by the implementation of <see cref = "GetByKey(int)" />.
		/// </summary>
		public virtual event EventHandler <GenericEventArgsWithOneParameter <Post , int>> PostRetrievedByKey;

		/// <summary>
		/// 	This event will raise before a list of <see cref = "Post" /> will be retrieved by the implementation of <see cref = " GetByMonth(int,int, int,int)" />.
		/// </summary>
		public virtual event EventHandler <CancelEventArgsWithOneParameter <Tuple <int , int>>> PostsRetrievingByMonth;

		/// <summary>
		/// 	This event will raise when a list of <see cref = "Post" /> will be retrieved by the implementation of <see cref = " GetByMonth(int,int, int,int)" />.
		/// </summary>
		public virtual event EventHandler <GenericEventArgsWithOneParameter <IPagedResult <Post> , Tuple <int , int>>> PostsRetrievedByMonth;

		/// <summary>
		/// 	This event will raise before a list of <see cref = "Post" /> will be retrieved by the implementation of <see cref = " GetPublished(int,int)" />.
		/// </summary>
		public virtual event EventHandler <CancelEventArgs> PostsPublishedRetrieving;

		/// <summary>
		/// 	This event will raise when a list of <see cref = "Post" /> will be retrieved by the implementation of <see cref = " GetPublished(int,int)" />.
		/// </summary>
		public virtual event EventHandler <GenericEventArgs <IPagedResult <Post>>> PostsPublishedRetrieved;

		/// <summary>
		/// 	This event will raise before a list of <see cref = "Post" /> will be retrieved by the implementation of <see cref = "GetCompleteList(int,int)" />.
		/// </summary>
		public virtual event EventHandler <CancelEventArgs> PostsRetrieving;

		/// <summary>
		/// 	This event will raise when a list of <see cref = "Post" /> will be retrieved by the implementation of <see cref = "GetCompleteList(int,int)" />.
		/// </summary>
		public virtual event EventHandler <GenericEventArgs <IPagedResult <Post>>> PostsRetrieved;

		/// <summary>
		/// 	This event will raise before a <see cref = "Post" /> will be deleted by the implementation of <see cref = "Delete(int)" />.
		/// </summary>
		public virtual event EventHandler <CancelEventArgsWithOneParameter <int>> PostDeleting;

		/// <summary>
		/// 	This event will raise after a <see cref = "Post" /> will be deleted by the implementation of <see cref = "Delete(int)" />.
		/// </summary>
		public virtual event EventHandler <GenericEventArgs <Post>> PostDeleted;

		/// <summary>
		/// 	This event will raise before a <see cref = "Post" /> will be saved by the implementation of <see cref = "Save(Post)" />.
		/// </summary>
		public virtual event EventHandler <CancelEventArgsWithOneParameter <Post>> PostSaving;

		/// <summary>
		/// 	This event will raise after a <see cref = "Post" /> will be saved by the implementation of <see cref = "Save(Post)" />.
		/// </summary>
		public virtual event EventHandler <GenericEventArgs <Post>> PostSaved;

		/// <summary>
		/// 	This event will raise before a <see cref = "Post" /> will be saved by the implementation of <see cref = "Update(Post)" />.
		/// </summary>
		public virtual event EventHandler <CancelEventArgsWithOneParameter <Post>> PostUpdating;

		/// <summary>
		/// 	This event will raise after a <see cref = "Post" /> will be saved by the implementation of <see cref = "Update(Post)" />.
		/// </summary>
		public virtual event EventHandler <GenericEventArgs <Post>> PostUpdated;

		#endregion

		#region Helpers

		void PrepareItemToSave(Post item) {
			if (item.Categories == null) {
				item.Categories = new List<Category> ( );
			}

			if (item.Categories.Count < 1) {
				item.Categories.Add ( CategoryRepository.GetDefaultCategory ( true ) );
			}

			if (string.IsNullOrEmpty ( item.Slug ))
				item.Slug = GenerateSlug ( item, GetBySlug );
		}

		/// <summary>
		/// Generates the slug.
		/// </summary>
		/// <param name="item">The item.</param>
		/// <param name="getbyslug">The getbyslug.</param>
		/// <returns></returns>
		internal static string GenerateSlug ( Item item, Func<string,bool, Item> getbyslug ) {
			
			// todo: refactor this in a utility class
			if (item == null)
				throw new ArgumentNullException ( "item" );

			const char WORD_SEPARATOR = '-';

			string entryName = RemoveNonWordCharacters ( item.Title );
			entryName = ReplaceSpacesWithSeparator ( entryName, WORD_SEPARATOR );
			entryName = ReplaceUnicodeCharacters ( entryName );
			entryName = HttpUtility.UrlEncode ( entryName );
			entryName = RemoveTrailingPeriods ( entryName );
			entryName = entryName.Trim ( new[]
			                             {
			                             	WORD_SEPARATOR
			                             } );
			entryName = RemoveDoubleCharacter ( entryName, '.' );
			entryName = RemoveDoubleCharacter ( entryName, WORD_SEPARATOR );

			if (entryName.IsNumber ( ))
				entryName = "n" + WORD_SEPARATOR + entryName;

			entryName = entryName.ToLower ( CultureInfo.InvariantCulture );

			Item element = getbyslug(entryName,true);
			int tryCount = 0;

			while (element != null) {
				if (element.Id == item.Id)
					break;

				switch (tryCount) {
					case 0:
						entryName = string.Concat ( entryName, WORD_SEPARATOR, "Again" );
						break;
					case 1:
						entryName = string.Concat ( entryName, WORD_SEPARATOR, "Yet", WORD_SEPARATOR, "Again" );
						break;
					case 2:
						entryName = string.Concat ( entryName, WORD_SEPARATOR, "And", WORD_SEPARATOR, "Again" );
						break;
					case 3:
						entryName = string.Concat ( entryName, WORD_SEPARATOR, "Once", WORD_SEPARATOR, "More" );
						break;
					case 4:
						entryName = string.Concat ( entryName, WORD_SEPARATOR, "Last", WORD_SEPARATOR, "One" );
						break;
					default:
						entryName = string.Concat(entryName, WORD_SEPARATOR, tryCount);
						break;
				}

				if (tryCount++ > 15)
					throw new InvalidItemSlugException ( );

				element = getbyslug(entryName,true);
			}

			return entryName;
		}

		internal static string ReplaceUnicodeCharacters ( string text ) {
			string normalized = text.Normalize ( NormalizationForm.FormKD );
			Encoding removal = Encoding.GetEncoding ( Encoding.ASCII.CodePage , new EncoderReplacementFallback ( string.Empty ) , new DecoderReplacementFallback ( string.Empty ) );
			byte[] bytes = removal.GetBytes ( normalized );
			return Encoding.ASCII.GetString ( bytes );
		}

		internal static string RemoveDoubleCharacter ( string text , char character ) {
			if ( text == null ) {
				throw new ArgumentNullException ( "text" );
			}

			if ( character == char.MinValue ) {
				return text;
			}

			char[] newString = new char[text.Length];
			int i = 0;

			bool lastCharIsOurChar = false;
			foreach ( char c in text ) {
				if ( c != character || !lastCharIsOurChar ) {
					newString[ i ] = c;
					i++;
				}
				lastCharIsOurChar = ( c == character );
			}

			return new string ( newString , 0 , i );
		}

		internal static string ReplaceSpacesWithSeparator ( string text , char wordSeparator ) {
			return text.Replace ( ' ' , wordSeparator );
		}

		internal static string RemoveNonWordCharacters ( string text ) {
			Regex regex = new Regex ( @"[\w\d\.\- ]+" , RegexOptions.Compiled );
			MatchCollection matches = regex.Matches ( text );
			StringBuilder cleansedText = new StringBuilder ( );

			foreach ( Match match in matches ) {
				if ( match.Value.Length > 0 ) {
					cleansedText.Append ( match.Value );
				}
			}
			return cleansedText.ToString ( );
		}

		internal static string RemoveTrailingPeriods ( string text ) {
			Regex regex = new Regex ( @"\.+$" , RegexOptions.Compiled );
			return regex.Replace ( text , string.Empty );
		}

		#endregion

	}
}